package com.example.auth.util;

import org.mindrot.jbcrypt.BCrypt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.MessageDigest;
import java.util.Base64;

public class PasswordUtils {
    private static final Logger logger = LoggerFactory.getLogger(PasswordUtils.class);
    private static final String LEGACY_SALT = "your_custom_salt_value"; // Salt value used in the old version

    /**
     * Encrypts the password using the BCrypt algorithm.
     * @param password The raw password.
     * @return The encrypted password.
     * @throws IllegalArgumentException if the password is null.
     * @throws RuntimeException if password encryption fails.
     */
    public static String encryptPassword(String password) {
        if (password == null) {
            logger.warn("Failed to encrypt password: Password is null");
            throw new IllegalArgumentException("Password cannot be null");
        }

        try {
            // Use BCrypt to encrypt, automatically generating the salt and including it in the result
            String hashedPassword = BCrypt.hashpw(password, BCrypt.gensalt(12));
            logger.debug("Password encrypted successfully");
            return hashedPassword;
        } catch (Exception e) {
            logger.error("An exception occurred during password encryption", e);
            throw new RuntimeException("Password encryption failed", e);
        }
    }

    /**
     * Verifies whether the password matches, supporting both the new BCrypt format and the old SHA-256 format.
     * @param rawPassword The raw password.
     * @param encodedPassword The encrypted password.
     * @return `true` if the passwords match, `false` otherwise.
     */
    public static boolean matches(String rawPassword, String encodedPassword) {
        if (rawPassword == null || encodedPassword == null) {
            logger.warn("Password verification failed: Raw password or encoded password is null");
            return false;
        }

        try {
            // First try to verify using BCrypt
            if (encodedPassword.startsWith("$2a$") || encodedPassword.startsWith("$2b$") || encodedPassword.startsWith("$2y$")) {
                boolean matches = BCrypt.checkpw(rawPassword, encodedPassword);
                logger.debug("Verifying password using BCrypt: {}", matches ? "Match" : "No Match");
                return matches;
            } else {
                // Try to verify using the old SHA-256 format
                logger.debug("Verifying password using the old format");
                String legacyHash = legacyEncrypt(rawPassword);
                boolean matches = legacyHash.equals(encodedPassword);
                logger.debug("Verifying password using the old format: {}", matches ? "Match" : "No Match");
                return matches;
            }
        } catch (Exception e) {
            logger.error("An exception occurred during password verification", e);
            return false;
        }
    }

    /**
     * Encrypts the password using the old SHA-256 algorithm for backward compatibility.
     * @param password The raw password.
     * @return The encrypted password in the old format.
     * @throws RuntimeException if old format password encryption fails.
     */
    private static String legacyEncrypt(String password) {
        try {
            // Combine the password and salt
            String saltedPassword = password + LEGACY_SALT;

            // Create a SHA-256 digester
            MessageDigest digest = MessageDigest.getInstance("SHA-256");

            // Calculate the hash value
            byte[] hash = digest.digest(saltedPassword.getBytes());

            // Convert the byte array to a Base64 string
            return Base64.getEncoder().encodeToString(hash);
        } catch (Exception e) {
            logger.error("Failed to encrypt password in the old format", e);
            throw new RuntimeException("Failed to encrypt password in the old format", e);
        }
    }

    /**
     * Checks whether the password needs to be upgraded to the new format.
     * @param encodedPassword The encrypted password.
     * @return `true` if an upgrade is needed, `false` otherwise.
     */
    public static boolean needsUpgrade(String encodedPassword) {
        if (encodedPassword == null) {
            return false;
        }

        // Check if it is already in BCrypt format
        return !(encodedPassword.startsWith("$2a$") ||
                encodedPassword.startsWith("$2b$") ||
                encodedPassword.startsWith("$2y$"));
    }
}